
#include <math.h>

#include <ft2build.h>
#include FT_FREETYPE_H  
#include FT_GLYPH_H
#include FT_STROKER_H

#define LAND_GRAPHICS_CORE

#include "land_graphics.h"

#define SLOT_RATE	64
#define SLOT_COUNT	(SLOT_RATE * SLOT_RATE)

typedef struct TNode TNode;
typedef struct {
	uint32_t ch;
	unsigned fsize;
	unsigned pos;
	int fx;
	int fy;
	int fw;
	int fh;
	unsigned width;
	unsigned height;
	LandVector uv;
	int next;
	int ref;
}TSlot;

struct TNode{
	TNode* next;
	unsigned slotSize;
	unsigned texSize;
	LandTexture* texture;
	TSlot slot[SLOT_COUNT];
	unsigned count;
	unsigned freeSlot;
};

struct LandFont {
	LandRef ref;
	unsigned height;
	unsigned width;
	TNode nodes[3];
	FT_Face face;
	FT_Stroker stroker;
	unsigned char* temp;
};

static struct {
	FT_Library ftLib;
}ctx = { 0 };

static LEVEL_SIZES[] = {
	16, 32, 64
};


static unsigned
hashCh(uint32_t ch, uint32_t fsize) {
	uint32_t data[] = { ch, fsize };
	char* p = (char*)data;
	size_t size = sizeof(data);
	uint32_t hash = 5381;
	for (size_t i = 0; i < size; i++)
		hash = ((hash << 5) + hash) + p[i];
	return hash % SLOT_COUNT;
}

static unsigned
calcLevel(unsigned fsize, int* level){
	fsize = fsize + (fsize % 2);
	if (fsize < 8) {
		*level = 0;
		fsize = 8;
	}
	else if (fsize >= 8 && fsize <= 16) {
		*level = 0;
	}
	else if (fsize > 16 && fsize <= 32) {
		*level = 1;
	}
	else if (fsize <= 64) {
		*level = 2;
	}
	else {
		fsize = 64;
		*level = 2;
	}
	return fsize;
}


static TSlot*
findSlot(TNode* node, uint32_t ch, unsigned fsize) {
	uint32_t hash = hashCh(ch, fsize);
	TSlot* slot = &node->slot[hash];
	while(slot) {
		if (slot->ch == ch && slot->fsize == fsize) {
			break;
		}
		else {
			slot = (slot->next >= 0) ? &node->slot[slot->next] : NULL;
		}
	}
	return slot;
}

static TSlot*
findFreeSlot(TNode* node, uint32_t ch, unsigned fsize) {
	uint32_t hash = hashCh(ch, fsize);
	TSlot* slot = &node->slot[hash];
	if (!slot->ref) {
		if (hash == node->freeSlot) {
			node->freeSlot--;
		}
		return slot;
	}
	while (slot->next >= 0) {
		slot = &node->slot[slot->next];
	}
	int pos = node->freeSlot;
	TSlot* n = &node->slot[pos];
	while (n->ref && pos > 0) {
		pos--;
		n = &node->slot[pos];
	}
	node->freeSlot = pos;
	slot->next = pos;
	return n;
}

static void
initNode(TNode* node, unsigned slotSize) {
	unsigned texSize = slotSize * SLOT_RATE;
	node->texture = landNewTexture(texSize, texSize, LAND_COLOR_A);
	landRetainRef(node->texture);
	node->slotSize = slotSize;
	node->texSize = texSize;
	node->count = 0;
	node->freeSlot = SLOT_COUNT - 1;
	node->next = NULL;
	memset(node->slot, 0, sizeof(node->slot));
	for(unsigned i = 0; i < SLOT_COUNT; i ++){
		node->slot[i].pos = i;
		node->slot[i].next = -1;
	}
}

static TNode*
newNode(LandFont* font, int level) {
	TNode* next = &font->nodes[level];
	while (next->next) {
		next = next->next;
	}
	TNode* node = malloc(sizeof(TNode));
	initNode(node, LEVEL_SIZES[level]);
	next->next = node;
	return node;
}

static TSlot*
newSlot(LandFont* font, TNode* node, uint32_t ch, unsigned fsize) {
	node->count++;
	TSlot* slot = findFreeSlot(node, ch, fsize);
	if (slot->ch == ch && slot->fsize == fsize) {
		slot->ref = 1;
		return slot;
	}
	slot->ch = ch;
	slot->fsize = fsize;
	slot->ref = 1;

	FT_Set_Pixel_Sizes(font->face, fsize, fsize);
	FT_UInt index = FT_Get_Char_Index(font->face, ch);
	FT_Load_Glyph(font->face, index, FT_LOAD_DEFAULT);
	FT_Glyph glyph;
	FT_Get_Glyph(font->face->glyph, &glyph);
	FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
	FT_Bitmap* bitmap = &(((FT_BitmapGlyph)glyph)->bitmap);

	FT_Size_Metrics* sm = &font->face->size->metrics;
	FT_Glyph_Metrics* gm = &font->face->glyph->metrics;
	FT_Pos width = bitmap->width;
	FT_Pos height = bitmap->rows;
	FT_Pos ascender = sm->ascender >> 6;
	FT_Pos descender = sm->descender >> 6;
	FT_Pos bx = gm->horiBearingX >> 6;
	FT_Pos by = gm->horiBearingY >> 6;
	FT_GlyphSlot ftSlot = font->face->glyph;
	
	slot->width = width;
	slot->height = height;
	slot->fw = gm->horiAdvance >> 6;
	slot->fh = gm->horiAdvance >> 6;
	slot->fx = bx;
	slot->fy = ascender - by;

	unsigned slotSize = node->slotSize;
	unsigned x = slot->pos % SLOT_RATE;
	unsigned y = slot->pos / SLOT_RATE;
	unsigned px = x * slotSize;
	unsigned py = y * slotSize;
	LandFloat texSize = (LandFloat)node->texSize;
	LandVector uv = {
		(LandFloat)px / texSize,
		(LandFloat)(py) / texSize,
		(LandFloat)(px + width) / texSize,
		(LandFloat)(py + height) / texSize,
	};
	unsigned pw = width;
	unsigned ph = height;
	if (pw > slotSize) { pw = slotSize; }
	if (ph > slotSize) { ph = slotSize; }
	slot->uv = uv;
	unsigned res, src;
	memset(font->temp, 0, slotSize * slotSize);
	for (unsigned iy = 0; iy < ph; iy++) {
		for (unsigned ix = 0; ix < pw; ix++) {
			res = (iy) * width + ix;
			src = (iy) * slotSize + ix;
			font->temp[src] = bitmap->buffer[res];
		}
	}
	FT_Done_Glyph(glyph);
	unsigned rect[] = { px, py, slotSize, slotSize };
	landUploadTexture(node->texture, LAND_COLOR_A, rect, font->temp);
	return slot;
}
static void
fullGlyph(LandGlyph* glyph, TSlot* slot) {
	glyph->x = slot->fx;
	glyph->y = slot->fy;
	glyph->width = slot->width;
	glyph->height = slot->height;
	glyph->fw = slot->fw;
	glyph->fh = slot->fh;
	glyph->uv = slot->uv;
}

static void
freeFont(LandRef* ref) {
	LandFont* font = (LandFont*)ref;
	FT_Done_Face(font->face);
	free(font->temp);
	TNode* next;
	TNode* temp;
	for (unsigned i = 0; i < 3; i++) {
		next = &font->nodes[i];
		landReleaseRef(next->texture);
		next = next->next;
		while (next) {
			temp = next->next;
			landReleaseRef(next->texture);
			free(next);
			next = temp;
		}
	}
}

static LandFont*
newFont(FT_Face face) {
	FT_Select_Charmap(face, FT_ENCODING_UNICODE);
	LandFont* font = landNewRef(sizeof(LandFont), freeFont);
	font->face = face;
	font->width = face->size->metrics.max_advance >> 6;
	font->height = face->size->metrics.height >> 6;
	font->temp = malloc(64 * 64);

	initNode(&font->nodes[0], 16);
	initNode(&font->nodes[1], 32);
	initNode(&font->nodes[2], 64);
	return (LandFont*)font;
}

LAND_GRAPHICS_API LandFont*
landNewFont(const char* file) {
	FT_Face face;
	if (FT_New_Face(ctx.ftLib, file, 0, &face)) {
		return NULL;
	}
	return newFont(face);
}

LAND_GRAPHICS_API LandFont*
landNewFontByData( const char* data, size_t size) {
	FT_Face face;
	if (FT_New_Memory_Face(
		ctx.ftLib, data, (FT_Long)size, 0, &face)) {
		return NULL;
	}
	return newFont(face);
}

LAND_GRAPHICS_API int
landRegFont(LandFont* font,
	uint32_t ch, unsigned fsize, LandGlyph* glyph) {
	int level = 0;
	fsize = calcLevel(fsize, &level);
	TNode* next = &font->nodes[level];
	TSlot* slot = NULL;
	while (next) {
		slot = findSlot(next, ch, fsize);
		if (slot) {
			break;
		}
		next = next->next;
	}
	if (slot) {
		slot->ref = 1;
		fullGlyph(glyph, slot);
		glyph->texture = next->texture;
		return 0;
	}
	next = &font->nodes[level];
	while (next && next->count >= SLOT_COUNT) {
		next = next->next;
	}
	if (!next) {
		next = newNode(font, level);
	}
	slot = newSlot(font, next, ch, fsize);
	fullGlyph(glyph, slot);
	glyph->texture = next->texture;
	return 0;
}

//LAND_GRAPHICS_API void
//landRecycleFont(LandFont* font, uint32_t ch, unsigned fsize) {
//	int level = 0;
//	fsize = calcLevel(fsize, &level);
//	TNode* next = &font->nodes[level];
//	TSlot* slot = NULL;
//	while (next) {
//		slot = findSlot(next, ch, fsize);
//		if (slot) {
//			break;
//		}
//		next = next->next;
//	}
//	if (slot) {
//		slot->ref--;
//		if (slot->ref <= 0 && slot->pos > next->freeSlot) {
//			next->freeSlot = slot->pos;
//		}
//	}
//}

LAND_GRAPHICS_API unsigned
landGetFontHeight(LandFont* font) {
	return font->height;
}


LAND_GRAPHICS_API LandTexture*
landGetFontTexture(LandFont* font) {
	return font->nodes[1].texture;
}

LAND_GRAPHICS_API void
landClearFont(LandFont* font) {
	TNode* next;
	for (unsigned i = 0; i < 3; i++) {
		next = &font->nodes[i];
		while (next) {
			for (unsigned i = 0; i < SLOT_COUNT; i++) {
				next->slot[i].ref = 0;
				next->slot[i].next = 0;
			}
			next->count = 0;
			next = next->next;
		}
	}
}

LAND_GRAPHICS_API void
landClearFontCap(LandFont* font) {
	TNode* next,* temp;
	for (unsigned i = 0; i < 3; i++) {
		next = &font->nodes[i];
		next->count = 0;
		for (unsigned i = 0; i < SLOT_COUNT; i++) {
			next->slot[i].ref = 0;
		}
		next = next->next;
		while (next) {
			temp = next->next;
			landReleaseRef(next->texture);
			free(next);
			next = temp;
		}
	}
}

LAND_GRAPHICS_API uint32_t
landUtf8(const unsigned char* p, unsigned* step) {
	uint32_t a = p[0];
	uint32_t b, c, d;
	if ((a & 0x80) == 0x00) {
		*step = 1;
		return  a;
	}else if ((a & 0xE0) == 0xC0) {
		a = p[0] & 0x1F;
		b = p[1] & 0x3F;
		*step = 2;
		return (a << 6) | b;
	}
	if ((a & 0xF0) == 0xE0) {
		a = p[0] & 0x0F;
		b = p[1] & 0x3F;
		c = p[2] & 0x3F;
		*step = 3;
		return (a << 12) | (b << 6) | c;
	}
	if ((a & 0xF8) == 0xF0) {
		a = p[0] & 0x07;
		b = p[1] & 0x3F;
		c = p[2] & 0x3F;
		d = p[3] & 0x3F;
		*step = 4;
		return (a << 18) | (b << 12) | (c << 8) | d;
	}
	return 0;
}

int
landInitFontContext(LandGraphics* config) {
	FT_Init_FreeType(&ctx.ftLib);
	return 0;
}

int
landExitFontContext(LandGraphics* config) {
	return 0;
}